Android提供了几种基本的动画:
- 帧动画
- 补间动画
- 属性动画
其中属性动画和补间动画比较常用,它们最大的区别在于补间动画并不会真正的改变View的属性,什么意思呢?比如通过补间动画将页面中的Button从左边移动到右边一段距离,如果此时点击Button,它不会对点击事件做出响应,这是因为button的作用区域(点击该区域依然可以触发click事件)依然在原来的位置,这是补间动画将其绘制在其原来的右边罢了。
本篇将首先从源码的角度对补间动画进行分析,属性动画会在下一篇中做介绍,至于补间动画的基本用法,这里就不多做介绍了。
在我们分析之前我们先提两个问题:
- 动画是如何进行绘制的?
- 动画是怎么计算每一帧的画面的?即某一个时刻View的位置的?
带着这两个问题,我们开始分析补间动画,这里首先从startAnimation开始分析,这是动画开始执行的方法。
1 | //frameworks\base\core\java\android\view\View.java |
startAnimation中主要做了两件事,第一个就是通过setAnimation设置当前View的动画,然后通过invalidate重绘制View。
1 | public void setAnimation(Animation animation) { |
setAnimation将animation保存在View的mCurrentAnimation以备使用。
接下来就是通过invalidate来重绘制View,这个我在专门的一篇中有所介绍,这里我简单说一下, invalidate实际上会从当前发起绘制的view开始向上寻找父View(ViewParent),然后和其父View计算总的脏区域,父View再计算根据得到的脏区域计算和其父View的脏区域,这个操作一直到ViewRootImpl,即View层级树的顶部管理者,它会根据计算的脏区域触发Draw操作。也就是会走到performTraversals中,这个流程想必大家都清楚了。
View在绘制完自身的内容后,会通过dispatchDrawl来绘制其子View,动画也就是在此时进行绘制的,
1 | // frameworks/base/core/java/android/view/ViewGroup.java |
dispatchDraw中会通过遍历孩子节点并未其调用drawChild来绘制子View
1 | boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { |
View的这个draw方法同draw(Canvas)的那个方法不同,它只是被ViewGroupd的drawChild调用,在这个方法里,我们可以看到绘制动画的方法,首先通过getAnimation获取到我们在startAnimation中设置的动画,然后交给drawAnimation计算动画的Transformation,它被存放在transformToApply中,如果有动画执行那么transformToApply肯定不为null,这里concatMatrix未true表示动画会改变变换矩阵,比如ScaleAnimation会改变变换的Matrix,而AlpahAnimation不会改变,为true这种情况会对canvas做一些列变换。
1 | ///frameworks/base/core/java/android/view/View.java |
这个方法是补间动画的核心处理方法,它首先对动画进行初始化initialize,接下来通过getTransformation计算动画的相关信息,并返回more,代表动画是否完成。如果是true表示还有动画需要执行,那么在最后会通过parent的invalidate重新发起绘制进行下一帧的绘制。
1 | public boolean getTransformation(long currentTime, Transformation outTransformation, |
getTransformation会根据当前时间和动画的持续时间执行时间流逝的进度,该进度随时间的流逝均匀的增加,如果该进度在0和1之间那么说明动画还在执行,如果动画此时还未开始会通过fireAnimationStart回调动画开始的回调,随后通过getInterpolation根据进度和插值器计算插值,插值和时间流逝的进度区别在于,插值可以根据数学模型生成任意的值,而时间流逝的进度值是客观不可变的。补间动画正是基于计算的插值来计算下一帧的动画的。计算完插值后通过applyTransformation来计算动画。我们可以看看TranslateAnimation是如何基于此计算动画的。
1 |
|
TranslateAnimation都是基于interpolatedTime计算动画偏移。如水平方向的偏移
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime) 这里的interpolatedTime可以看作是插值器计算的动画进度,它从0f-1.0f。
完成这一步,就可以通过view的父view发起绘制动画的请求,这是通过parent.invalidate方法来发起的。